/*
 * Licensed to the Tai Ping Jin Ke
 *
 * Copyright (c) 2022 .
 * All rights reserved.
 * 项目名称：太平金科-基础组件封装层-SpringBoot组件封装-缺省实现
 * 版权说明：本软件属太平金融科技服务(上海)有限公司所有，在未获得太平金融科技服务(上海)有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.taipingframework.boot.web.interceptors;

import com.taipingframework.boot.web.support.WebMvcHelper;
import com.taipingframework.utility.constant.ApplicationConstant;
import com.taipingframework.utility.thread.threadlocal.TransmittableThreadLocalHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.request.async.WebAsyncTask;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;

/**
 * 请注意，多个拦截器的执行顺序只与在注册时的添加顺序有关
 */
// implements HandlerInterceptor
// extends HandlerInterceptorAdapter
@Slf4j
@Component
public class DefaultInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (WebMvcHelper.skipIntercept(request, handler)) {
            return true;
        }

        String[] stackTraceArray = Arrays.stream(Thread.currentThread().getStackTrace())
                .map(StackTraceElement::toString)
                .toArray(String[]::new);
        log.debug("DefaultInterceptor.preHandle\r\n请求路径：{}\r\n\r\tat {}", request.getRequestURL(), String.join("\r\n\r\tat ", stackTraceArray[1]));

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        if (WebMvcHelper.skipIntercept(request, handler)) {
            return;
        }

        // api接口对外抛出异常后统一由全局异常处理程序包装最终的响应报文。
        // api接口发生异常时，后置拦截逻辑将不被执行！
        String[] stackTraceArray = Arrays.stream(Thread.currentThread().getStackTrace())
                .map(StackTraceElement::toString)
                .toArray(String[]::new);
        log.debug("DefaultInterceptor.postHandle\r\n\r\tat {}", String.join("\r\n\r\tat ", stackTraceArray[1]));
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        if (WebMvcHelper.skipIntercept(request, handler)) {
            return;
        }

        //* 非异步请求，接口响应耗时 以及 响应结果 由自定义AOP切面`HttpAspect.java`打印日志
        ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        if (isAsyncReturnType(handler)) {
            //* 记录异步请求响应结果
            if (Objects.nonNull(responseWrapper)) {
                String responseBody = new String(responseWrapper.getContentAsByteArray(), StandardCharsets.UTF_8.name());
                log.info("响应报文response={}", responseBody);
            }
            //* 获取时间戳，记录异步请求总耗时
            log.info("本次请求 共耗时 {} 毫秒",
                    System.currentTimeMillis() - Long.parseLong(TransmittableThreadLocalHelper.getEnvironment().get(ApplicationConstant.START_REQUEST_TIME_KEY)));
        } else {
            String[] stackTraceArray = Arrays.stream(Thread.currentThread().getStackTrace())
                    .map(StackTraceElement::toString)
                    .toArray(String[]::new);
            log.debug("DefaultInterceptor.afterCompletion\r\n\r\tat {}", String.join("\r\n\r\tat ", stackTraceArray[1]));
        }

        //* 为了适配异步请求的场景，特将编码`responseWrapper.copyBodyToResponse();`从过滤器移动到拦截器中！
        //* 注意，ContentCachingResponseWrapper 在取完数据之后需要调用copyBodyToResponse()方法，
        //* 只有这样响应里面才会有数据返回，否则响应体里面 是没有数据的
        if (Objects.nonNull(responseWrapper)) {
            log.debug("DefaultInterceptor.afterCompletion - 将缓冲区数据写入输出流");
            responseWrapper.copyBodyToResponse();
        }
    }

    private static final List<Class<?>> CLASS_LIST = Arrays.asList(Callable.class, WebAsyncTask.class, DeferredResult.class, CompletableFuture.class, Mono.class, Flux.class);

    /**
     * 判断是否为异步请求的返回类型
     */
    private static boolean isAsyncReturnType(Object handler) {
        if (handler instanceof HandlerMethod) {
            Class<?> parameterType = ((HandlerMethod) handler).getReturnType().getParameterType();
            return CLASS_LIST.contains(parameterType);
        }

        return false;
    }

}
